import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import * as gc from 'src/app/global/const';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ArrayutilsService } from './arrayutils.service';
import { StringUtilsService } from './string-utils.service';

@Injectable({
  providedIn: 'root'
})
export class MysqlService {

  // MYSQL 数据库中数据类型映射到 Angular 的数据类型
  dataTypeMap = {
    "varchar":"string",
    "int":"number",
    "float":"number",
    "datetime":"Date",
    "timestamp":"Date",
    "tinyint":"boolean",
    "decimal":"number",
    "bit":"boolean",
    "BIT":"boolean",
  }

  // MYSQL 数据库数据类型映射到 Mybatis 的数据类型
  dbDataType2MybatisDataType = {
    'int':'INTEGER',
    'varchar':'VARCHAR',
    'datetime':'TIMESTAMP',
    'tinyint':'BIT',
    'decimal':'DECIMAL',
    'float':'REAL'
  }

  // MYSQL 数据库中数据类型对应 JAVA 的数据类型
  dbDataType2JavaDataType = {
    'varchar':'java.lang.String',
    'int':'java.lang.Integer'
  }

  objType = {'database':'database','table':'table','field':'field'}
  
  sql = {
    'SHOW DATABASES':'select * from information_schema.`SCHEMATA` ORDER BY schema_name ASC;',

    'SHOW TABLES':`SELECT table_schema,table_name,create_time,update_time,
    table_collation,table_comment,engine
    FROM information_schema.tables
    WHERE table_schema='weixin_cloud' AND table_type='base table' ORDER BY table_name;`,

    'GET_DATABASE_TABLES':`select a.SCHEMA_NAME AS schema_name,b.table_name,b.table_comment
    from information_schema.\`SCHEMATA\` a
    LEFT JOIN information_schema.tables b
    ON a.SCHEMA_NAME = b.table_schema
    WHERE 1=1;`,

    // 返回的字段有：auto_key,schema_name,table_name,table_comment,column_name,column_type,
    // data_type,column_comment,extra,column_key
    'GET_TABLE_FIELDS':`select * from information_schema.\`COLUMNS\` WHERE table_schema='DBNAME' 
      AND table_name='TABLENAME';`,

      // c.extra = auto_increment 表示该字段是自增字段
      // c.column_key = PRI 表示该字段是主键
    'GET_DB_TABLES_FIELDS':`SELECT @auto_key:= @auto_key + 1 AS auto_key,d.*
    FROM (
    select a.SCHEMA_NAME AS schema_name,b.table_name,b.table_comment,
    c.column_name,c.column_type,c.data_type,c.column_comment,c.extra,
    c.column_key
    from information_schema.\`SCHEMATA\` a
    LEFT JOIN information_schema.tables b
    ON a.SCHEMA_NAME = b.table_schema
    LEFT JOIN information_schema.\`COLUMNS\` c
    ON a.SCHEMA_NAME = c.table_schema
    AND b.table_name = c.table_name
    WHERE 1=1
    ORDER BY a.SCHEMA_NAME,b.table_name,c.ordinal_position
    ) d,(SELECT @auto_key:=0) e;`
  }

  url = {
    'queryForList':gc.BACKEND_SERVER + '/dynamicDBController/queryForList'
  }

  rs:any[];// 请求后端获取到的用于构建树的原始记录集

  constructor(
    private http:HttpClient,
    private arrayService:ArrayutilsService,
    private stringService:StringUtilsService
  ) { }

  // 获取服务器中所有数据库名称列表
  // 返回的单个元素的格式是：
  //{CATALOG_NAME: "def", SCHEMA_NAME: "atools", DEFAULT_CHARACTER_SET_NAME: "utf8mb4", DEFAULT_COLLATION_NAME: "utf8mb4_croatian_ci", SQL_PATH: null}
  getDatabases(dbConfig:DBConfig):Observable<any[]>{
    let queryParams = new DynamicQueryParams();
    queryParams.sql = this.sql["SHOW DATABASES"];
    queryParams.dbConfig = dbConfig;

    return this.http.post<any[]>(this.url.queryForList,queryParams);
  }

  convertDB2Nodes(dbs$:Observable<any[]>){
    let nodes = dbs$.pipe(map(
      (res:any[])=>{
        let ns:any[] = []
        res.forEach(ele => {
          ns.push({ 'title':ele.SCHEMA_NAME,'key':ele.SCHEMA_NAME });
        });
        return ns;
      }
    ));

    return nodes;
  }

  // 将数据表流的数据转换为 nodes，返回流
  convertTables2Nodes(tables$:Observable<any[]>):Observable<any[]>{
    let nodes = tables$.pipe(map(
      (res:any[])=>{
        let ns = [];
        res.forEach(ele => {
          ns.push({ 'title':ele.table_name + ',' + ele.table_comment,'key':ele.table_name });
        });
        return ns;
      }
    ))

    return nodes;
  }

  // 获取指定数据库下的所有表
  getTables(dbConfig:DBConfig,dbName:string):Observable<any[]>{
    let sql = this.sql["SHOW TABLES"].replace('weixin_cloud',dbName);

    let queryParams = new DynamicQueryParams();
    queryParams.sql = sql;
    queryParams.dbConfig = dbConfig;

    return this.http.post<any[]>(this.url.queryForList,queryParams);
  }


  private getDatabasesTables(dbConfig:DBConfig):Observable<any[]>{
    let queryParams = new DynamicQueryParams();
    queryParams.sql = this.sql.GET_DATABASE_TABLES;
    queryParams.dbConfig = dbConfig;

    return this.http.post<any[]>(this.url.queryForList,queryParams);
  }

  private getAllFields(dbConfig:DBConfig):Observable<any[]>{
    let queryParams = new DynamicQueryParams();
    queryParams.sql = this.sql.GET_DB_TABLES_FIELDS;
    queryParams.dbConfig = dbConfig;

    return this.http.post<any[]>(this.url.queryForList,queryParams);
  }

  // 构建数据库、表、字段构成的树
  buildTableTree(dbConfig:DBConfig){
    return this.getDatabasesTables(dbConfig).pipe(map(
      ( res:any[] )=>{
        return this.convertFlat2Tree(res);
      }
    ));
  }

  // 构建：数据库、表、字段   构成的树
  buildFieldTree(dbConfig:DBConfig){
    let ret$ = this.getAllFields(dbConfig).pipe(map(
      (res:any[])=>{
        this.rs = res;
        return this.convertFlat2FieldTreeWithKey(res);
        // return this.arrayService.buildTree(res,['schema_name','table_name','column_name'],'auto_key');
      }
    ))

    return ret$;
  }

  // 传递进来的数组的格式是：
  // INSERT INTO MY_TABLE(schema_name, table_name, table_comment, column_name, column_type, column_comment) 
  // VALUES ('weixin_log', 'subscribe', '关注公众号的日志', 'openid', 'varchar(300)', '用户openid');
  private convertFlat2FieldTree(rs:any[]):any[]{
    let distDB = [];
    let distTable = [];

    rs.forEach(ele => {
      // 构建去重的数据库数组
      if(distDB.length<=0) {
        distDB.push(ele.schema_name);
      }
      if(!this.arrayService.has(distDB,ele.schema_name)){
        distDB.push(ele.schema_name);
      }

      // 构建去重的表数组
      if(distTable.length<=0) distTable.push({'dbName':ele.schema_name,'tableName':ele.table_name});
      let tableExists = distTable.some((ele1,index,array)=>{
        return ele1.tableName === ele.table_name;
      })
      if( !tableExists ) distTable.push({'dbName':ele.schema_name,'tableName':ele.table_name});

    });

    if(distDB.length<=0){
      console.log('没有数据库！');
      return;
    }

    let tree = [];
    distDB.forEach(ele => {
      let dbNode = {'title':ele,'key':ele,'children':[],'origin':'0'};// origin = 0 表示是数据库

      let tableNodes = [];
      let tables = distTable.filter((item)=>{
        return item.dbName === ele;
      })

      tables.forEach(ele1 => {
        let fields = rs.filter((item)=>{
          return item.table_name === ele1.tableName;
        })

        let fieldNodes = [];
        fields.forEach((ele2) => {
          let title = ele2.column_name + ' - ' + ele2.column_type + ' - ' + ele2.column_comment;
          fieldNodes.push({'title':title,'key':ele2.column_name,'isLeaf':'true','origin':'2'});// origin =2 表示是字段
        });
        let tableNode = {'title':ele1.tableName,'key':ele1.tableName,'children':fieldNodes,'origin':'1'};// origin =1 表示是数据表
        tableNodes.push(tableNode);
      });

      dbNode.children = tableNodes;
      tree.push(dbNode);
    });


    return tree;
  }


  private buildOrigin(type:string,value:string){
    return {'type':type,'value':value};
  }

  // 将异步获取来的数据库以及表转换为树形
  
  // ========= 使用 level 创建树形数据 Begin ===========================
  buildTreeByLevel(rs:any[],levels:string[]){
    let i = 0;
    for(i=0;i<levels.length;i++){
      let fieldName = levels[i];
      let curDistArray = [];
      if(curDistArray.length<=0) curDistArray.push();
    }
  }



  buildCurNode(row:any,levels:string[],curLevel:number):string{
    let i = 0;
    let ret='{';
    console.log(row['schema_name']);
    console.log(row['table_name']);
    
    for(i=0;i<=curLevel;i++){
      let fieldName = levels[i];
      let value = row[fieldName];
      ret = ret + `'value${i}':'${value}',`;
    }
    ret = ret + `'children':[]`;
    // 去掉最后的逗号
    if( ret.substr(ret.length-1,1)===',' ) ret = ret.substr(0,ret.length-1)
    ret = ret + '}';
    return ret;
  }
  // ========= 使用 level 创建树形数据 End ===========================


  private convertFlat2FieldTreeWithKey(rs:any[]):any[]{
    let rsDB = [];
    let rsTable = [];
    
    // 构建去重的数据库数组、表数组
    rs.forEach(ele => {
      // 构建去重的数据库数组
      if(rsDB.length<=0) rsDB.push({'dbName':ele.schema_name,'autoKey':ele.auto_key});
      let dbExists = rsDB.some(
        (dbRow,index,array) =>{
          return dbRow.dbName === ele.schema_name;
        }
      )
      if( !dbExists ) rsDB.push({'dbName':ele.schema_name,'autoKey':ele.auto_key});

      // 构建去重的表数组
      if(rsTable.length<=0) rsTable.push({'dbName':ele.schema_name,'tableName':ele.table_name,'autoKey':ele.auto_key});
      let tableExists = rsTable.some((ele1,index,array)=>{
        return ele1.tableName === ele.table_name && ele1.dbName === ele.schema_name ;
      })
      if( !tableExists ) rsTable.push({'dbName':ele.schema_name,'tableName':ele.table_name,'autoKey':ele.auto_key});

    });

    // 没有数据库则不创建，退出
    if(rsDB.length<=0){
      console.log('没有数据库！');
      return;
    }

    let tree = [];
    
    rsDB.forEach(eleDB => {// 遍历数据库数组
      // 构建单个数据库节点 - 子节点留空
      // let dbKey = "schema_name=" + eleDB;
      let dbNode = {'title':eleDB.dbName,'key':eleDB.autoKey,'children':[],'origin':this.buildOrigin(this.objType.database,eleDB.dbName)};// 本地枚举设置类型 = 数据库
      
      let tables = rsTable.filter((item)=>{// 获取当前数据库下的所有表
        return item.dbName === eleDB.dbName;
      })

      let tableNodes = [];
      tables.forEach(eleTable => {
        let rsFields = rs.filter((item)=>{// 从原始记录集中获取指定数据库、表下的所有字段
          return item.table_name === eleTable.tableName && item.schema_name === eleTable.dbName;
        })

        let fieldNodes = [];
        rsFields.forEach((eleField) => {
          let title = eleField.column_name + ' - ' + eleField.column_type + ' - ' + eleField.column_comment;
          fieldNodes.push({'title':title,'key':eleField.auto_key,'isLeaf':'true','origin':this.buildOrigin(this.objType.field,eleField.column_name)});
        });
        let tableNode = {'title':eleTable.tableName,'key':eleTable.autoKey,'children':fieldNodes,'origin':this.buildOrigin(this.objType.table,eleTable.tableName)};
        tableNodes.push(tableNode);
      });

      dbNode.children = tableNodes;
      tree.push(dbNode);
    });


    return tree;
  }

  convertFlat2Tree(array:any[]):any[]{
    let distArray = [];
    let count = 0;
    array.forEach(ele => {// 在字段 schema_name 上去重后构建一个新的数组
      if( distArray.length<=0 ) distArray.push(ele.schema_name);

      if(!this.arrayService.has(distArray,ele.schema_name)){
        distArray.push(ele.schema_name);
      }
    });

    if( distArray.length<=0 ){
      console.log('没有过滤出数据库列表！');
      return null;
    }

    let tree = [];

    distArray.forEach(ele => {// 遍历去重的数组分别构建其下的子节点
      let node = { 'title':ele,'key':ele,'children':[] };

      let children = [];
      array.filter(( item )=>{// 遍历所有数据构建指定数据库下的表名数组
        if( item.schema_name === ele ){
          children.push({'title':item.table_name,'key':item.table_name,'isLeaf':'false'});
        }
      })  

      node.children = children;
      tree.push(node);
    });
    
    return tree;
  }

  getTableFields(dbConfig:DBConfig,dbName:string,tableName:string):Observable<any[]>{
    let sql = this.sql.GET_TABLE_FIELDS.replace('DBNAME',dbName).replace('TABLENAME',tableName);
    let queryParams = new DynamicQueryParams();
    queryParams.sql = sql;
    queryParams.dbConfig = dbConfig;

    return this.http.post<any[]>(this.url.queryForList,queryParams);
  }

  isTable(origin:any):boolean{
    if( origin.type === this.objType.table ) 
      return true;
    else
      return false;
  }

  isDatabase(origin){
    if( origin.type === this.objType.database ) 
      return true;
    else
      return false;
  }

  isField(origin){
    if( origin.type === this.objType.field ) 
      return true;
    else
      return false;
  }

  createAngularModel(key):string{
    let row = this.rs.filter(ele=>ele.auto_key === key)[0];
    let dbName = row["schema_name"];
    let tableName = row["table_name"];

    return this.createAngularModel_go(dbName,tableName);
  }

  createMybatisSaveBatch(key,upperAttribute?:boolean):string{
    let row = this.rs.filter(ele=>ele.auto_key === key)[0];
    let dbName = row["schema_name"];
    let tableName = row["table_name"];

    return this.createMybatisSaveBatch_go(dbName,tableName,upperAttribute);
  }

  /**
   * 根据 MySQL 表生成 Java 中的 Mybatis 保存+修改 当行数据的 SQL 语句。
   * @param key 查询数据库获取字段列表时手动添加的自增的序号
   * @param modelPackage Java后端项目中模型类的包名
   * @param upperAttribute 生成的 Mybatis SQL 语句中字段映射为的模型类的属性是否采用字段原始字母
   *              true 表示采用字段原始字母 - MySQL 中字段 is_test 在对应的 Java POJO 类
   *              中该属性也是 is_test，false 表示将 is_test 转换为 isTest作为类的属性
   */
  createMybatisSaveOne(key,modelPackage:string,upperAttribute?:boolean):string{
    let row = this.rs.filter(ele=>ele.auto_key === key)[0];
    let dbName = row["schema_name"];
    let tableName = row["table_name"];

    return this.createMybatisSaveOne_go(dbName,tableName,modelPackage,upperAttribute);
  }

  // 获取表字段数组中主键字段名称
  private getPrimaryKeyFieldName(fields:any[]){
    if( fields === undefined || fields === null || fields.length <= 0 ) return '';
    let ret = fields.filter(item => item.column_key + ''.toUpperCase() === 'PRI');
    if( ret.length<=0 ) return '';
    return ret[0].column_name;
  }

  private getPrimaryKeyJavaDataType(fields:any[]){
    if( fields === undefined || fields === null || fields.length <= 0 ) return '';
    let ret = fields.filter(item => item.column_key + ''.toUpperCase() === 'PRI');
    if( ret.length<=0 ) return '';
    let dbDataType = ret[0].data_type + ''.toLowerCase();
    let javaDataType = this.dbDataType2JavaDataType[dbDataType];
    return javaDataType;
  }

  private getPrimaryKeyAngularDataType(fields:any[]){
    if( fields === undefined || fields === null || fields.length <= 0 ) return '';
    let ret = fields.filter(item => item.column_key + ''.toUpperCase() === 'PRI');
    if( ret.length<=0 ) return '';
    let dbDataType = ret[0].data_type + ''.toLowerCase();
    let angularDataType = this.dataTypeMap[dbDataType];
    return angularDataType;
  }

  // 生成 Mybatis 单行数据的新增/修改 SQL 语句
  private createMybatisSaveOne_go(dbName:string,tableName:string,
    modelPackage:string,upperAttribute?:boolean):string{

    if( this.rs.length<=0 ){
      console.log('没有缓存的树形原始记录集！');
      return;
    }

    let curRs = this.rs.filter(// 过滤获得指定表下的所有字段
      val => val.table_name === tableName && val.schema_name === dbName
    )

    if( curRs.length<=0 ) return '';

    let pk = this.getPrimaryKeyFieldName(curRs);
    let pkJavaDataType = this.getPrimaryKeyJavaDataType(curRs);

    let ret:string = `
<!-- 新增成功返回1，修改成功返回2 -->
<insert id="saveOne" parameterType="${modelPackage}">
    <!--<selectKey resultType="${pkJavaDataType}" keyProperty="${pk}" order="AFTER">
      SELECT LAST_INSERT_ID()
    </selectKey>-->

    insert into ${tableName} 
    <trim prefix="(" suffix=")" suffixOverrides=",">`

    curRs.forEach(ele => {
      // SQL 语句中的字段 - 保持原样
      let fieldName = ele["column_name"];

      // SQL 语句中的 POJO 类属性 - 可能要去掉下划线并且大写首字母
      let attri = upperAttribute === true?this.stringService.toPOJOAttribute(fieldName):fieldName;
      ret = ret + `
      <if test="${attri} != null"> ${fieldName}, </if>`
    });
    ret = ret + `
    </trim>
    values
    <trim prefix="(" suffix=")" suffixOverrides=",">`

    curRs.forEach(ele=>{
      // SQL 语句中的字段 - 保持原样
      let fieldName = ele["column_name"];

      // SQL 语句中的 POJO 类属性 - 可能要去掉下划线并且大写首字母
      let attri = upperAttribute === true?this.stringService.toPOJOAttribute(fieldName):fieldName;
      
      ret = ret + `
        <if test="${attri} != null"> #{${attri}},</if>`
    });
    ret = ret + `
    </trim>
    ON DUPLICATE KEY UPDATE
    <trim suffixOverrides=",">`

    curRs.forEach(item=>{
      // SQL 语句中的字段 - 保持原样
      let fieldName = item["column_name"];

      // SQL 语句中的 POJO 类属性 - 可能要去掉下划线并且大写首字母
      let attri = upperAttribute === true?this.stringService.toPOJOAttribute(fieldName):fieldName;
      
      let extra = item["extra"] === null?'':item["extra"];
      // 非自增列才修改数值
      if ( extra !=='auto_increment' ){
        ret = ret + `
        <if test="${attri} != null"> ${fieldName} = #{${attri}}, </if>`;
      }
    });

    ret = ret + `
    </trim>
</insert>`

    return ret;
  }

  private createMybatisSaveBatch_go(dbName:string,tableName:string,upperAttribute?:boolean):string{
    if( this.rs.length<=0 ){
      console.log('没有缓存的树形原始记录集！');
      return;
    }

    let curRs = this.rs.filter(// 过滤获得指定表下的所有字段
      val => val.table_name === tableName && val.schema_name === dbName
    )

    if( curRs.length<=0 ) return '';
    let pk = this.getPrimaryKeyFieldName(curRs);
    let pkJavaDataType = this.getPrimaryKeyJavaDataType(curRs);

    let ret:string = `
<!-- 批量写入时全部成功也只会返回1，中间有出现异常只影响后面的语句不影响之前的写入 -->
<insert id="saveBatch" parameterType="java.util.List">
    <!--<selectKey resultType="${pkJavaDataType}" keyProperty="${pk}" order="AFTER">
      SELECT LAST_INSERT_ID()
    </selectKey>-->

    <foreach collection ="list" item="ele" index= "index" separator =";">
    insert into ${tableName} 
    <trim prefix="(" suffix=")" suffixOverrides=",">`

    curRs.forEach(ele => {
      let fieldName = ele["column_name"];
      let attri = upperAttribute === true?this.stringService.toPOJOAttribute(fieldName):fieldName;

      ret = ret + `
      <if test="ele.${attri} != null"> ${fieldName}, </if>`
    });
    ret = ret + `
    </trim>
    values
    <trim prefix="(" suffix=")" suffixOverrides=",">`

    curRs.forEach(ele=>{
      let fieldName = ele["column_name"];
      let attri = upperAttribute === true?this.stringService.toPOJOAttribute(fieldName):fieldName;

      ret = ret + `
        <if test="ele.${attri} != null"> #{ele.${attri}},</if>`
    });
    ret = ret + `
    </trim>
    ON DUPLICATE KEY UPDATE
    <trim suffixOverrides=",">`

    curRs.forEach(item=>{
      let fieldName = item["column_name"];
      let attri = upperAttribute === true?this.stringService.toPOJOAttribute(fieldName):fieldName;
      let extra = item["extra"] === null?'':item["extra"];
      // 非自增列才修改数值
      if ( extra !=='auto_increment' ){
        ret = ret + `
        <if test="ele.${attri} != null"> ${fieldName} = #{ele.${attri}}, </if>`;
      }
    });

    ret = ret + `
    </trim>
    </foreach>
</insert>`

    return ret;
  }

  // 创建指定表的 Angular Model
  private createAngularModel_go(dbName:string,tableName:string):string{
    if( this.rs.length<=0 ){
      console.log('没有缓存的树形原始记录集！');
      return;
    }

    let curRs = this.rs.filter(// 过滤获得指定表下的所有字段
      val => val.table_name === tableName && val.schema_name === dbName
    )
      
    
    let modelString = `// Author：chanchaw
// Generator Version：1.0 - 2019年11月16日 16点46分
// This Model Created：${new Date()}
export class ${ tableName }{`

    curRs.forEach(ele => {
      let dataType = this.dataTypeMap[ele.data_type];
      modelString = modelString + `
  ${ele["column_name"]}:${dataType};`
    });

    modelString = modelString + `
}`

    return modelString;
  }


  createMybatisSelectAll(key):string{
    let row = this.rs.filter(ele=>ele.auto_key === key)[0];
    let dbName = row["schema_name"];
    let tableName = row["table_name"];

    return this.createMybatisSelectAll_go(dbName,tableName);
  }

  private createMybatisSelectAll_go(dbName:string,tableName:string):string{
    if( this.rs.length<=0 ){
      console.log('没有缓存的树形原始记录集！');
      return;
    }

    let curRs = this.rs.filter(// 过滤获得指定表下的所有字段
      val => val.table_name === tableName && val.schema_name === dbName
    )

    if( curRs.length<=0 ) return '';
    let pk = this.getPrimaryKeyFieldName(curRs);
    let ret = `<select id="selectAll" resultMap="BaseResultMap">`;
    ret = ret + `
    Select * From ${tableName} where 1=1 Order By seq,${pk}`;
    ret = ret + `
</select>`;
    return ret;
  }


  createSpringDao(key,modelName:string):string{
    let row = this.rs.filter(ele=>ele.auto_key === key)[0];
    let dbName = row["schema_name"];
    let tableName = row["table_name"];

    return this.createSpringDaoGo(dbName,tableName,modelName);
  }

  createSpringDaoGo(dbName:string,tableName:string,modelName:string):string{
    if( this.rs.length<=0 ){
      console.log('没有缓存的树形原始记录集！');
      return;
    }

    let curRs = this.rs.filter(// 过滤获得指定表下的所有字段
      val => val.table_name === tableName && val.schema_name === dbName
    )
          
    let ret = `List<${modelName}> selectAll();
int saveOne(${modelName} record);
int saveBatch(List<${modelName}> list);`;
    return ret;
  }

  createSpringController(key,modelName:string):string{
    let row = this.rs.filter(ele=>ele.auto_key === key)[0];
    let dbName = row["schema_name"];
    let tableName = row["table_name"];

    return this.createSpringControllerGo(dbName,tableName,modelName);
  }

  createSpringControllerGo(dbName:string,tableName:string,modelName:string):string{
    if( this.rs.length<=0 ){
      console.log('没有缓存的树形原始记录集！');
      return;
    }

    let curRs = this.rs.filter(// 过滤获得指定表下的所有字段
      val => val.table_name === tableName && val.schema_name === dbName
    )
          
    let lowerModelName:string = modelName.substr(0,1).toLowerCase();
    lowerModelName = lowerModelName + modelName.substr(1);

    let pk = this.getPrimaryKeyFieldName(curRs);
    let pkJavaDataType = this.getPrimaryKeyJavaDataType(curRs);
    let ret = `
    @CrossOrigin
    @RestController
    @RequestMapping("/${lowerModelName}")
    public class ${modelName}Controller {
    
        @Autowired
        private ${modelName}Service service;
    
        @DeleteMapping("/{${pk}}")
        public JsonResult deleteByPrimaryKey(@PathVariable  ${pkJavaDataType} ${pk}){
            return JsonResult.ok(service.deleteByPrimaryKey(${pk}));
        }
    
        @GetMapping("/{${pk}}")
        public JsonResult selectByPrimaryKey(@PathVariable ${pkJavaDataType} ${pk}){
            return JsonResult.ok(service.selectByPrimaryKey(${pk}));
        }
    
        @GetMapping("/a")
        public JsonResult selectAll(){
            return JsonResult.ok(service.selectAll());
        }
    
        @PostMapping("/o")
        public JsonResult saveOne(@RequestBody  ${modelName} record){
            return JsonResult.ok(service.saveOne(record));
        }
    
        @PostMapping("/l")
        public JsonResult saveBatch(@RequestBody List<${modelName}> list){
            return JsonResult.ok(service.saveBatch(list));
        }

        @PostMapping("/insert")
        public JsonResult insert(@RequestBody ${modelName} record){
            return JsonResult.ok(service.insertSelective(record));
        }

        @PostMapping("/update")
        public JsonResult update(@RequestBody ${modelName} record){
            return JsonResult.ok(service.updateByPrimaryKeySelective(record));
        }

    
    }`;
    return ret;
  }



  /**
   * 生成 Angular Service
   * @param key 左侧树形结构中的主键（非数据库主键）
   */
  createAngularService(key):string{
    let row = this.rs.filter(ele=>ele.auto_key === key)[0];
    let dbName = row["schema_name"];
    let tableName = row["table_name"];

    return this.createAngularServiceGo(dbName,tableName);
  }

  /**
   * 生成 Angular Service
   * @param dbName 数据库名称
   * @param tableName 表名称
   */
  private createAngularServiceGo(dbName:string,tableName:string):string{
    if( this.rs.length<=0 ){
      console.log('没有缓存的树形原始记录集！');
      return;
    }

    let curRs = this.rs.filter(// 过滤获得指定表下的所有字段
      val => val.table_name === tableName && val.schema_name === dbName
    )
    
    let modelName:string = this.stringService.toUpperWords(tableName);
    let path_pre:string = modelName.charAt(0).toLowerCase() + modelName.slice(1);
    let pkFieldName:string = this.getPrimaryKeyFieldName(curRs);
    let pkFieldDataType:string = this.getPrimaryKeyAngularDataType(curRs);


    let ret = `
    private path_pre = '${path_pre}';

    private url = {
      'deleteByPrimaryKey':gc.BACKEND_SERVER_ROOT + '/' + this.path_pre,
      'selectByPrimaryKey':gc.BACKEND_SERVER_ROOT + '/' + this.path_pre,
      'selectAll':gc.BACKEND_SERVER_ROOT + '/' + this.path_pre + '/a',
      'saveOne':gc.BACKEND_SERVER_ROOT + '/' + this.path_pre,
      'insert':gc.BACKEND_SERVER_ROOT + '/' + this.path_pre + '/insert',
      'update':gc.BACKEND_SERVER_ROOT + '/' + this.path_pre + '/update',
    }
    constructor(
      private http:HttpClient
    ) { }
  
    deleteByPrimaryKey(${pkFieldName}:${pkFieldDataType}):Observable<JsonResult>{
      const url = this.url.deleteByPrimaryKey + '/' + ${pkFieldName};
      return this.http.delete<JsonResult>(url);
    }
  
    selectByPrimaryKey(${pkFieldName}:${pkFieldDataType}):Observable<JsonResult>{
      const url = this.url.selectByPrimaryKey + '/' + ${pkFieldName};
      return this.http.get<JsonResult>(url);
    }
  
    selectAll():Observable<JsonResult>{
      return this.http.get<JsonResult>(this.url.selectAll);
    }
  
    saveOne(record:${modelName}):Observable<JsonResult>{
      if( record === undefined || record === null ){
        let jr:JsonResult = new JsonResult();
        jr.code = 777;
        jr.msg = '数据为空，不可保存！';
        return of(jr);
      }
      if( record.iid <=0 )
        return this.insert(record);
      else
        return this.update(record);
    }
    
    private insert(record:${modelName}):Observable<JsonResult>{
      return this.http.post<JsonResult>(this.url.insert,record);
    }

    private update(record:${modelName}):Observable<JsonResult>{
      return this.http.put<JsonResult>(this.url.update,record);
    }

    `;

    return ret;
  }


  
  /**
   * 生成表字段构成的字符串，默认以逗号间隔
   * @param dbName 数据库名称
   * @param tableName 表名称
   * @param delimiter 间隔符号，可选参数，默认逗号
   */
  createFieldString(key):string{
    let row = this.rs.filter(ele=>ele.auto_key === key)[0];
    let dbName = row["schema_name"];
    let tableName = row["table_name"];
    let delimiter = ',';

    if( dbName.length<=0 ) return '';
    if( tableName.length<=0 ) return '';

    let curRs = this.rs.filter(// 过滤获得指定表下的所有字段
      val => val.table_name === tableName && val.schema_name === dbName
    )

    if( curRs.length<=0 ) return '';
    let ret:string = '';
    curRs.forEach(ele => {
      ret = ret + ele.column_name + delimiter;
    });

    if( ret.substr(ret.length-1) === delimiter ) ret = ret.substr(0,ret.length-1);
    return ret;
  }

  // 生成单表类型的模板文件代码
  createSingleTemplate():string{
    let ret:string = `
    <!-- 
      方法说明：
      addEmptyRow：新增一个空行
      refreshTable：刷新表格数据，从远端读取数据更新到本地显示。
      colHeaderAlign：根据字段名返回该字段列头的对齐方式
      startEdit：开启一行的编辑模式

      属性说明：
      vari.tableData：表格的数据源
      vari.tableStyle:业务数据表在样式表 table_style 中的设置的参数的集合，一行是针对业务表一个字段的配置
     -->
    <div>
      <a (click)="addEmptyRow()"><i nz-icon nzType="plus" nzTheme="outline"></i>新增行</a>&nbsp;
      <a (click)="refreshTable()"><i nz-icon nzType="sync" nzTheme="outline"></i>&nbsp;刷新</a>
    </div>&nbsp;
    <nz-table #zorroTable nzBordered [nzData]="vari.tableData" nzSize="small">
      <thead>
        <tr>
          <th nzWidth="6%">#</th>
            <ng-container *ngFor="let item of vari.tableStyle">
            <th *ngIf="!item.immutable" [nzWidth]="item.width_percent_str" 
              [nzAlign]="colHeaderAlign(item.field_name)">
              {{ item.text }}
            </th>
          </ng-container>
        </tr>
      </thead>
    <tbody>
      <!-- 
        class.strip 是斑马纹，将下面的样式代码拷贝到css文件中
        /* 表格组件斑马纹 */
        .strip{
            background-color: #eff4f9;
        }
      -->
      <tr *ngFor="let data of zorroTable.data;let rowIndex=index" (dblclick)="startEdit(rowIndex,data['uuid'],$event)"
        (mouseover)="mouseOver(mouseOverUUID = data.uuid)" [class.strip]="rowIndex%2 == 0">
        <td><!-- 序号 + 图标列 -->
          <div *ngIf="!cache.detail[data['uuid']].edit;else editIconTpl">
            <div *ngIf="data.uuid === mouseOverUUID;else rowIndexTpl" nz-row nzType="flex" 
              nzJustify="space-between">
              <div nz-col nzSpan="10"><!-- 删除 nz-tooltip nzTooltipTitle="删除"-->
                <a 
                  nz-popconfirm
                  nzPopconfirmTitle="您确定要删除当前行?"
                  (nzOnConfirm)="deleteRow(rowIndex,data['uuid'])"
                  (nzOnCancel)="cancelDeleteRow()"
                  nzPopconfirmPlacement="top"
                ><i nz-icon nzType="delete" nzTheme="outline"></i></a>
              </div>

              <div nz-col nzSpan="10"><!-- 启动编辑 nz-tooltip nzTooltipTitle="复制"  -->
                <a 
                  (click)="copyRow(rowIndex,data['uuid'])"><i nz-icon nzType="copy" nzTheme="outline"></i></a>
                  <!-- 由于空间有限只能放两个图标，一开始是：删除、编辑，后来修改为：删除、复制，通过双击行开启编辑状态 -->
                <!-- <a (click)="startEdit(rowIndex,data['uuid'])"><i nz-icon nzType="edit" nzTheme="outline"></i></a> -->
              </div>
            </div>
            <ng-template #rowIndexTpl>{{rowIndex+1}}</ng-template>
          </div>

          <ng-template #editIconTpl>
            <div nz-row nzType="flex" nzJustify="space-between">
              <div nz-col nzSpan="10"><!-- 取消编辑 nz-tooltip nzTooltipTitle="取消编辑" -->
                <a 
                  (click)="cancelEdit(rowIndex,data['uuid'])"><i nz-icon nzType="redo" nzTheme="outline"></i></a>
              </div>


              <div nz-col nzSpan="10"><!-- 保存 nz-tooltip nzTooltipTitle="保存" -->
                <a 
                 (click)="saveEdit(rowIndex,data['uuid'])"><i nz-icon nzType="save" nzTheme="outline"></i></a>
              </div>
            </div>
          </ng-template>
        </td>

        <ng-container *ngFor="let tsRow of vari.tableStyle">
          <td *ngIf="!tsRow.immutable" [nzAlign]="colContentAlign(tsRow.field_name)">
            <!-- 显示模式 -->
            <ng-container *ngIf="!cache.detail[data['uuid']].edit;else editTpl">

              <ng-container *ngIf="tsRow.date_pipe;else plainShowTpl">
                {{ data[tsRow.field_name] | date:tsRow.date_formatter }}
              </ng-container>
              <ng-template #plainShowTpl>
                {{ convertRaw2Show(data,tsRow) }}
              </ng-template>

            </ng-container>
            
            <ng-template #editTpl><!-- 编辑模式，使用 switch 枚举用于编辑的控件类型 -->

              <span [ngSwitch]="tsRow.ctl_type">
              <p *ngSwitchCase="'optsEnum'"><!-- 下拉选择 - 枚举 -->
                <nz-select nzShowSearch nzAllowClear nzPlaceHolder="" [(ngModel)]="cache.detail[data.uuid].data[tsRow.field_name]" style="width: 100%;">
                  <nz-option *ngFor="let item of opts_enum[tsRow.field_name]" [nzLabel]="item.label" [nzValue]="item.value"></nz-option>
                </nz-select>

              </p>
              <p *ngSwitchCase="'date'"><!-- 日期控件 -->
                <nz-date-picker [(ngModel)]="cache.detail[data.uuid].data[tsRow.field_name]"
                  nzFormat="yyyy-MM-dd"></nz-date-picker>
              </p>
              <p *ngSwitchDefault><!-- 常规的 input -->
                <input type="text" nz-input [(ngModel)]="cache.detail[data.uuid].data[tsRow.field_name]" 
                  (ngModelChange)="inputChange(cache.detail[data.uuid].data,tsRow)"/>
              </p>

              <p *ngSwitchCase="'locked'"><!-- 被锁定 -->
                {{ cache.detail[data.uuid].data[tsRow.field_name] }}
                <!-- {{ resolveFormula(data,tsRow) }} -->
              </p>

              </span>

            </ng-template>

          </td>
        </ng-container>

      </tr>

      <!-- 统计 -->
      <tr>
        <td>合计：</td>
        <ng-container *ngFor="let tsRow of vari.tableStyle">
          <td *ngIf="!tsRow.immutable" [nzAlign]="'right'">
            <ng-container *ngIf="tsRow.statistics;else elseBlock">
              {{ vari.statistics[tsRow.field_name]?.label }}
            </ng-container>
            
            <ng-template #elseBlock></ng-template>
          </td>
        </ng-container>
        
      </tr>
    </tbody>
  </nz-table>
    `


    let ret1:string = `
  <!-- 
  方法说明：
  addEmptyRow：新增一个空行
  refreshTable：刷新表格数据，从远端读取数据更新到本地显示。
  colHeaderAlign：根据字段名返回该字段列头的对齐方式
  startEdit：开启一行的编辑模式

  属性说明：
  vari.tableData：表格的数据源
  vari.tableStyle:业务数据表在样式表 table_style 中的设置的参数的集合，一行是针对业务表一个字段的配置
  -->
<div>
  <a (click)="addEmptyRow()"><i nz-icon nzType="plus" nzTheme="outline"></i>&nbsp;新增行</a>&nbsp;
  <a (click)="refreshTable()"><i nz-icon nzType="sync" nzTheme="outline"></i>&nbsp;刷新</a>
</div>&nbsp;
<nz-table #zorroTable nzBordered [nzData]="vari.tableData" nzSize="small">
  <thead>
    <tr class="strip">
      <th nzWidth="6%">#</th><!-- 序号列 -->
      <ng-container *ngFor="let item of vari.tableStyle"><!-- 遍历样式表构建列头，不创建immutable列 -->
        <th *ngIf="!item.immutable" [nzWidth]="item.width_percent_str" 
          [nzAlign]="colHeaderAlign(item.field_name)">
          {{ item.text }}
        </th>
      </ng-container>
    </tr>
  </thead>

  <tbody>
    <!-- 
      class.strip 是斑马纹，将下面的样式代码拷贝到css文件中
      /* 表格组件斑马纹 */
      .strip{
          background-color: #eff4f9;
      }
    -->
    <tr *ngFor="let rowData of zorroTable.data;let rowIndex=index" (dblclick)="startEdit(rowIndex,rowData['uuid'],$event)"
      (mouseover)="mouseOver(mouseOverUUID = rowData.uuid)" [class.strip]="rowIndex%2 !== 0">
      <td><!-- 序号 + 图标列 -->
        <!-- 序号列 - 显示状态下的两个图标：删除、复制 -->
        <div *ngIf="!cache.detail[rowData['uuid']].edit;else editIconTpl">
          <div *ngIf="rowData.uuid === mouseOverUUID;else rowIndexTpl" nz-row nzType="flex" nzJustify="space-between">
            <div nz-col nzSpan="10">
              <a 
                nz-popconfirm
                nzPopconfirmTitle="您确定要删除当前行?"
                (nzOnConfirm)="deleteRow(rowIndex,rowData['uuid'])"
                (nzOnCancel)="cancelDeleteRow()"
                nzPopconfirmPlacement="top"
              ><i nz-icon nzType="delete" nzTheme="outline"></i></a>
            </div>

            <div nz-col nzSpan="10">
              <a 
                (click)="copyRow(rowIndex,rowData['uuid'])"><i nz-icon nzType="copy" nzTheme="outline"></i></a>
                <!-- 由于空间有限只能放两个图标，一开始是：删除、编辑，后来修改为：删除、复制，通过双击行开启编辑状态 -->
              <!-- <a (click)="startEdit(rowIndex,rowData['uuid'])"><i nz-icon nzType="edit" nzTheme="outline"></i></a> -->
            </div>
          </div>
          <ng-template #rowIndexTpl>{{rowIndex+1}}</ng-template>
        </div>

        <!-- 序号列 - 编辑状态下的两个图标：取消、保存 -->
        <ng-template #editIconTpl>
          <div nz-row nzType="flex" nzJustify="space-between">
            <div nz-col nzSpan="10"><!-- 取消编辑 nz-tooltip nzTooltipTitle="取消编辑" -->
              <a 
                (click)="cancelEdit(rowIndex,rowData['uuid'])"><i nz-icon nzType="redo" nzTheme="outline"></i></a>
            </div>


            <div nz-col nzSpan="10"><!-- 保存 nz-tooltip nzTooltipTitle="保存" -->
              <a 
                (click)="saveEdit(rowIndex,rowData['uuid'])"><i nz-icon nzType="save" nzTheme="outline"></i></a>
            </div>
          </div>
        </ng-template>
      </td>

      <!-- 序号之后的所有数据列 -->
      <ng-container *ngFor="let tsRow of vari.tableStyle">
        <td *ngIf="!tsRow.immutable" [nzAlign]="colContentAlign(tsRow.field_name)">
          <!-- 显示模式 -->
          <ng-container *ngIf="!cache.detail[rowData['uuid']].edit;else editTpl">

            <ng-container *ngIf="tsRow.date_pipe;else plainShowTpl">
              {{ rowData[tsRow.field_name] | date:tsRow.date_formatter }}
            </ng-container>
            <ng-template #plainShowTpl>
              {{ convertRaw2Show(rowData,tsRow) }}
            </ng-template>

          </ng-container>
          
          <ng-template #editTpl><!-- 编辑模式，使用 switch 枚举用于编辑的控件类型 -->

            <span [ngSwitch]="tsRow.ctl_type">
            <p *ngSwitchCase="'optsEnum'"><!-- 下拉选择 - 枚举 -->
              <nz-select nzShowSearch nzAllowClear nzPlaceHolder="" [(ngModel)]="cache.detail[rowData.uuid].data[tsRow.field_name]" style="width: 100%;">
                <nz-option *ngFor="let item of opts_enum[tsRow.field_name]" [nzLabel]="item.label" [nzValue]="item.value"></nz-option>
              </nz-select>

            </p>
            <p *ngSwitchCase="'date'"><!-- 日期控件  [nzFormat]="'yyyy-MM-dd HH:mm:ss'" -->
              <nz-date-picker [nzFormat]="'yyyy-MM-dd'" [(ngModel)]="cache.detail[rowData.uuid].data[tsRow.field_name]"></nz-date-picker>
            </p>
            <p *ngSwitchDefault><!-- 常规的 input -->
              <input type="text" nz-input [(ngModel)]="cache.detail[rowData.uuid].data[tsRow.field_name]" 
                (ngModelChange)="inputChange(cache.detail[rowData.uuid].data,tsRow)"/>
            </p>

            <p *ngSwitchCase="'locked'"><!-- 被锁定 -->
              <!-- {{ cache.detail[rowData.uuid].data[tsRow.field_name] }} -->
              <!-- {{ resolveFormula(rowData,tsRow) }} -->
              {{ breadField.showLocked(rowData,tsRow) }}
            </p>

            </span>

          </ng-template>

        </td>
      </ng-container>

    </tr>

    <!-- 统计 -->
    <!-- <tr>
      <td>合计：</td>
      <ng-container *ngFor="let tsRow of vari.tableStyle">
        <td *ngIf="!tsRow.immutable" [nzAlign]="'right'">
          <ng-container *ngIf="tsRow.statistics;else elseBlock">
            {{ vari.statistics[tsRow.field_name]?.label }}
          </ng-container>
          
          <ng-template #elseBlock></ng-template>
        </td>
      </ng-container>
      
    </tr> -->
  </tbody>

</nz-table>
`
    // 在 2020年1月18日22:58:15 第二次制作，所以返回 ret1
    return ret1;
  }

  // 生成单表类型的控制器代码
  createSingleController(tableName:string,modelName:string,detailService:string):string{
    // 下面的正则表达式写在字符串中
    // 由于\是转义字符，所以要结果字符串中出现该字符
    // 那么需要连续两个该符号
    let regSplit:string = '/\\+|\\-|\\*|\\//g';
    let ret = `
    vari:{// 变量集合 - 属性集合
      'tableName':string,// 表名称
      'tableData':${modelName}[],// 表格组件的数据源
      'tableStyle':TableStyle[],// 列样式表
      'mouseOver_rowIndex':number,// 鼠标悬停的行号
      'emptyRowCount':number, // 空白行数
      'statistics':{ [key:string]:{value:number,label:string} } // 有统计的字段的集合
    } = { 'tableName':'${tableName}','tableData':[],'tableStyle':[],
      mouseOver_rowIndex:-1,'emptyRowCount':1,
      'statistics':{}};

    // 样式表 table_style 在字段 opts 上有值的字段的集合，构建为下拉数据源
    // 格式是：value1=label1,value2=label2...
    // 实例：  1=单程,2=往返
    // 同时实现在表格的对应列上出现下拉数据，将 value 保存到数据源，显示模式下显示 label
    opts_enum:{ [key:string]:{label:string,value:number}[] } = {};

    // 表格编辑用缓存数据
    cache:{
      detail:{ [key:string]:{ edit:boolean,data:${modelName} } }
    } = {
      'detail':{}
    }

    mouseOverUUID:string= '';

    constructor(
      private noti:NzNotificationService,
      private tableStyleService:TableStyleService,
      private detailService:${detailService},
      private breadField:BreadFieldService
    ) { 
      this.init();
    }

    ngOnInit() {
    }

    init(){
      this.initOpts();
      this.pullTableData();
    }

    // 使用表格数据更新编辑用缓存数据
    updateEditCache(){
      this.vari.tableData.forEach(
        (ele:${modelName}) => {
          this.cache.detail[ele.uuid] = {
            edit:false,data:{...ele}
          }
      });
      
    }

    // 初始化获取所有带选择数据和字段样式数据
    initOpts(){
      this.tableStyleService.selectByTableName(this.vari.tableName).subscribe(
        ( res:JsonResult ) =>{
          if( res.code !==1 ){
            this.msg(res.msg);
            return
          }

          this.vari.tableStyle = res.data;// 样式字段集合保存到属性中
          this.vari.tableStyle.forEach(ele => {
            ele.width_percent_str = ele.width_percent + '%';
            this.resolveEnumOpts(ele);// 表 table_style 在字段 opts 上有值的话构建为下拉选择数据
          });
        },error => this.msg('获取表格样式数据时出现异常！')
      )
    }

    // 刷新表格数据 - 从远端获取数据更新本地视图
    pullTableData(){
      this.detailService.selectAll().subscribe(
        ( res:JsonResult ) =>{
          if( res.code !== 1 ){
            this.msg(res.msg);
            return
          }

          this.vari.tableData = res.data;
          this.updateEditCache();// 更新编辑用缓存数据
          // this.buildEmptyRows();
          this.updateStatistics();// 更新统计数据
        },error => this.msg('获取表格数据时出现异常！')
      )
    }

    // 构建空白行
    buildEmptyRows(){
      if( this.vari.emptyRowCount === undefined ||this.vari.emptyRowCount === null || this.vari.emptyRowCount<=0 ) return

      let emptyRows:${modelName}[] = [];
      let lastUUID:string = '';
      for(let i=0;i<this.vari.emptyRowCount;i++){
        let newRecord = new ${modelName}();
        lastUUID = newRecord.uuid;
        emptyRows.push(newRecord); 
      }

      this.vari.tableData = this.vari.tableData.concat(emptyRows);
      this.updateEditCache();
      this.cache.detail[lastUUID].edit = true;
    }


    /**
     * 解析来自数据库端的枚举类型的选项，构建为本地数组
     * @param enums 格式为：value1=label1,value2=label2
     */
    resolveEnumOpts(record:TableStyle){
      let enums = record.opts;
      if( enums === undefined || enums === null || enums.length<=0 ) {
        return
      }
      if( record.immutable === true ) return // 是系统字段则不构建

      let arr = enums.split(',');
      let ret:{label:string,value:number}[] = [];
      arr.forEach(ele => {
        let val = ele.split('=');
        ret.push({ value:parseInt(val[0]),label:val[1] });
      });

      this.opts_enum[record.field_name] = ret;
    }


    msg(content:string){
      this.noti.blank('通知',content);
    }

    printOpts(){
      console.log(this.opts_enum['one_way']);
    }


    mouseOver(uuid:string){
      // this.mouseOverUUID = uuid;
    }

    mouseLeave(rowIndex:number){
      //this.msg('鼠标走开了 --- ' + rowIndex);
    }

    /**
     * 新增一个空行，并且处于编辑状态
     */
    addEmptyRow(){
      let record:${modelName} = new ${modelName}();
      let uuid:string = record.uuid;
      this.cache.detail[uuid] = {
        edit:true,data:record
      };

      //  let ret = this.vari.tableData.splice(this.vari.tableData.length,0,record);
      this.vari.tableData = this.vari.tableData.concat(record);
    }

    hasEditableRow():boolean{
      let data = this.cache.detail;
      if( data === undefined || data === null ) return false;

      for(let attr in this.cache.detail){
        if( this.cache.detail[attr].edit )
          return true;
      }

      return false;
    }

    startEdit(rowIndex:number,uuid:string,event){
      event.preventDefault();
      this.cache.detail[uuid].edit = true;
      // this.fillDefault(rowIndex,uuid);
      // this.msg('开启编辑状态的行是：' + rowIndex + ',' + uuid);
    }

    cancelEdit(rowIndex:number,uuid:string){
      this.cache.detail[uuid].edit = false;
    }

    saveEdit(rowIndex:number,uuid:string){
      let record:${modelName} = this.cache.detail[uuid].data;
      this.detailService.saveOne(record).subscribe(
        ( res:JsonResult ) => {
          if( res.code !=1 ){
            this.msg(res.msg);
            return
          }

          this.msg('保存完毕！');
          let newArray = this.vari.tableData.filter(
            item => item.uuid !== uuid
          )
          let newRecord:${modelName} = res.data;
          newRecord.uuid = UUID.UUID();
          newArray.splice(rowIndex,0,{...newRecord})
          this.vari.tableData = newArray;
          this.updateEditCache();// 更新编辑用缓存数据
          this.updateStatistics();// 更新统计数据
          this.cache.detail[newRecord.uuid].edit = false;
        },error => this.msg('保存时出现异常！')
      )

    }

    // 删除行 - 仅前端
    deleteRow_front(rowIndex:number,uuid:string){
      this.vari.tableData = this.vari.tableData.filter(
        item => item.uuid !== uuid
      )
    }
    
    /**
     * 删除一行数据：
     * 1. 如果没有iid ，那么只要删除前端 + 更新编辑用缓存数据
     * 2. 删除后端成功后，删除前端 + 更新编辑用缓存数据
     * @param rowIndex 行序号，从0开始
     * @param uuid 行属性uuid
     */
    deleteRow(rowIndex:number,uuid:string){
      let iid = this.vari.tableData[rowIndex].iid;
      if( iid === undefined || iid === null || iid<=0 ){// 仅仅删除前端
        this.deleteRow_front(rowIndex,uuid);
        this.updateEditCache();
        this.updateStatistics();
      }else{// 删除后端成功后：删除前端 + 更新编辑用缓存
        this.detailService.deleteByPrimaryKey(this.vari.tableData[rowIndex].iid).subscribe(
          ( res:JsonResult ) => {
            if( res.code != 1 ){
              this.msg(res.msg);
              return
            }
    
            this.deleteRow_front(rowIndex,uuid);
            this.updateEditCache();
            this.updateStatistics();
            this.msg('删除完毕！');
          },error => this.msg('删除时出现异常！')
        )
      }
    }

    cancelDeleteRow(){
      // console.log('取消了删除行');
    }

    copyRow(rowIndex:number,uuid:string){
      let iid = this.vari.tableData[rowIndex].iid;
      if( iid === undefined || iid === null || iid <=0 ){
        this.msg('该行还未保存，不可复制！');
        return
      }

      let newRecord:${modelName} = {...this.vari.tableData[rowIndex]};
      newRecord.iid = 0;
      this.detailService.insert(newRecord).subscribe(
        ( res:JsonResult ) =>{
          if( res.code !==1 ){
            this.msg(res.msg);
            return
          }

          newRecord.uuid = UUID.UUID();
          this.vari.tableData = this.vari.tableData.concat(newRecord);
          this.updateEditCache();
          this.msg('复制完毕！');
        },error => this.msg('复制时出现异常！')
      )
    }


  /**
   * 将数据库 table_style 表中设置为枚举类型的下拉选项的字段
   * 显示为其值对应的文本
   * @param record 行数据，单个实体类对象
   * @param fieldName 字段名称
   */
    convertRaw2Show(record:${modelName},tsRow:TableStyle):string{
      return this.breadField.convertRaw2Show(record,tsRow);
    }


    // 从显示模式切换到编辑模式时为下拉框填充数据
    fillDefault(rowIndex:number,uuid:string){
      console.log('进入fillDefault');
      this.vari.tableStyle.forEach(ele => {
        switch(ele.ctl_type){
          case('optsEnum'):
            console.log('是 optsEnum 类型的控件，赋值前：' + this.cache.detail[uuid].data[ele.field_name]);
            this.cache.detail[uuid].data[ele.field_name] = this.vari.tableData[rowIndex][ele.field_name];
            console.log('赋值后：' + this.cache.detail[uuid].data[ele.field_name]);
            break;
          default:
            break;
        }
      });
      
    }

    setTest(){
      let aa:string = '1+2-3*4/2';
      const gg = ${regSplit};
      let bb = aa.split(gg);

      console.log(bb);
    }

    // 最初的设计是视图中存在数据表中没有的字段，完全从其他字段通过逻辑或者算数运算得来
    // 后来发现统计上会有问题，放弃该做法，采用较死板的方式：
    // 算数运算中在等于号左边的字段也要在数据表中有字段存储数据，在编辑模式中为不可编辑。
    generateCol(dataRow:${modelName},tsRow:TableStyle){
      return this.breadField.getGeneratedResult(dataRow,tsRow);
    }

    colHeaderAlign(fieldName:string):string{
      let tsRow:TableStyle = this.vari.tableStyle.filter( item => item.field_name === fieldName )[0];
      return tsRow.align_header;
    }

    colContentAlign(fieldName:string):string{
      let tsRow:TableStyle = this.vari.tableStyle.filter( item => item.field_name === fieldName )[0];
      return tsRow.align_cell;
    }

    // 更新统计数据
    updateStatistics(){
      if( this.vari.tableData.length<=0 ) return

      this.vari.statistics = null;
      this.vari.statistics = {};

      // 遍历字段构建汇总对象
      let arr = this.vari.tableStyle.filter(item=>item.statistics);
      if( arr.length<=0 ) return;
      arr.forEach(ele => {
        this.vari.statistics[ele.field_name] = { value:0,label:'' };
      });
      

      this.vari.tableData.forEach(ele => {
        this.vari.tableStyle.forEach(ele1 => {
          let field:string = ele1.field_name;
          if( ele1.statistics ){
            let curValue:number = ele[field] === undefined?0:ele[field];
            curValue = curValue === null?0:curValue;
            this.vari.statistics[field].value = this.vari.statistics[field].value + curValue;
            this.vari.statistics[field].label = Number(this.vari.statistics[field].value).toFixed(1);
          }
        });
      });
    }


    refreshTable(){
      this.pullTableData();
    }


    resolveFormula(dataRow:${modelName},tsRow:TableStyle):string{
      if( !tsRow.statistics ) return

      return this.breadField.useFormatter(dataRow,tsRow)
    }

    // 编辑状态下，编辑文本变动则对应修改其相关公式关联的字段的值
    inputChange(dataRow:${modelName},tsRow:TableStyle){
      let arr:{ fieldName:string,value:number }[] = this.breadField.triggerFormula(dataRow,tsRow,this.vari.tableStyle);
      if( arr.length<=0 ) return
      arr.forEach(ele => {
        this.cache.detail[dataRow.uuid].data[ele.fieldName] = ele.value;
      });
    }
    `;


    let ret1:string =`
    import { Component, OnInit } from '@angular/core';
import { LifeSchedule, LifeScheduleService } from 'src/app/service/life-schedule.service';
import { TableStyle, TableStyleService } from 'src/app/service/table-style.service';
import { NzNotificationService } from 'ng-zorro-antd';
import { BreadFieldService } from 'src/app/service/bread-field.service';
import { JsonResult } from 'src/app/model/JsonResult';
import { UUID } from 'angular2-uuid';

@Component({
  selector: 'app-life-schedule',
  templateUrl: './life-schedule.component.html',
  styleUrls: ['./life-schedule.component.css']
})
export class LifeScheduleComponent implements OnInit {

  vari:{// 变量集合 - 属性集合
    'tableName':string,// 表名称
    'tableData':${modelName}[],// 表格组件的数据源
    'tableStyle':TableStyle[],// 列样式表
    'mouseOver_rowIndex':number,// 鼠标悬停的行号
    'emptyRowCount':number, // 空白行数
    'statistics':{ [key:string]:{ label:string,value:number } } // 有统计的字段的集合
  } = { 'tableName':'${tableName}','tableData':[],'tableStyle':[],
    mouseOver_rowIndex:-1,'emptyRowCount':1,
    'statistics':{}};

  // 样式表 table_style 在字段 opts 上有值的字段的集合，构建为下拉数据源
  // 格式是：value1=label1,value2=label2...
  // 实例：  1=单程,2=往返
  // 同时实现在表格的对应列上出现下拉数据，将 value 保存到数据源，显示模式下显示 label
  opts_enum:{ [key:string]:{label:string,value:string}[] } = {};

  // 表格编辑用缓存数据
  cache:{
    detail:{ [key:string]:{ edit:boolean,data:${modelName} } }
  } = {
    'detail':{}
  }

  mouseOverUUID:string= '';

  constructor(
    private noti:NzNotificationService,
    private tableStyleService:TableStyleService,
    private detailService:${detailService},
    public breadField:BreadFieldService
  ) { 
    this.init();
  }

  ngOnInit() {
  }

  init(){
    this.initOpts();
    this.pullTableData();
  }

  // 使用表格数据更新编辑用缓存数据
  updateEditCache(){
    this.vari.tableData.forEach(
      (ele:${modelName}) => {
        this.cache.detail[ele.uuid] = {
          edit:false,data:{...ele}
        }
    });
    
  }

  // 初始化获取所有带选择数据和字段样式数据
  initOpts(){
    this.tableStyleService.selectByTableName(this.vari.tableName).subscribe(
      ( res:JsonResult ) =>{
        if( res.code !==1 ){
          this.msg(res.msg);
          return
        }

        this.vari.tableStyle = res.data;// 样式字段集合保存到属性中
        this.vari.tableStyle.forEach(ele => {
          ele.width_percent_str = ele.width_percent + '%';
          this.resolveEnumOpts(ele);// 表 table_style 在字段 opts 上有值的话构建为下拉选择数据
        });
      },error => this.msg('获取表格样式数据时出现异常！')
    )
  }

  // 刷新表格数据 - 从远端获取数据更新本地视图
  pullTableData(){
    this.detailService.selectAll().subscribe(
      ( res:JsonResult ) =>{
        if( res.code !== 1 ){
          this.msg(res.msg);
          return
        }

        this.vari.tableData = res.data;
        // 遍历数组为 uuid 赋值
        this.vari.tableData.forEach(ele => {
          ele.uuid = UUID.UUID();
        });
        this.updateEditCache();// 更新编辑用缓存数据
        // this.buildEmptyRows();
        this.updateStatistics();// 更新统计数据
      },error => this.msg('获取表格数据时出现异常！')
    )
  }

  // 构建空白行
  buildEmptyRows(){
    if( this.vari.emptyRowCount === undefined ||this.vari.emptyRowCount === null || this.vari.emptyRowCount<=0 ) return

    let emptyRows:${modelName}[] = [];
    let lastUUID:string = '';
    for(let i=0;i<this.vari.emptyRowCount;i++){
      let newRecord = new ${modelName}();
      lastUUID = newRecord.uuid;
      emptyRows.push(newRecord); 
    }

    this.vari.tableData = this.vari.tableData.concat(emptyRows);
    this.updateEditCache();
    this.cache.detail[lastUUID].edit = true;
  }


  /**
   * 解析来自数据库端的枚举类型的选项，构建为本地数组
   * @param enums 格式为：value1=label1,value2=label2
   */
  resolveEnumOpts(record:TableStyle){
    let enums = record.opts;
    if( enums === undefined || enums === null || enums.length<=0 ) {
      return
    }
    if( record.immutable === true ) return // 是系统字段则不构建

    let arr = enums.split(',');
    let ret:{label:string,value:string}[] = [];
    arr.forEach(ele => {
      let val = ele.split('=');
      ret.push({ value:String(val[0]),label:val[1] });
    });

    this.opts_enum[record.field_name] = ret;
  }


  msg(content:string){
    this.noti.blank('通知',content);
  }

  printOpts(){
    console.log(this.opts_enum['one_way']);
  }


  mouseOver(uuid:string){
    // this.mouseOverUUID = uuid;
  }

  mouseLeave(rowIndex:number){
    //this.msg('鼠标走开了 --- ' + rowIndex);
  }

  /**
   * 新增一个空行，并且处于编辑状态
   */
  addEmptyRow(){
    let record:${modelName} = new ${modelName}();
    let uuid:string = record.uuid;
    this.cache.detail[uuid] = {
      edit:true,data:record
    };

    //  let ret = this.vari.tableData.splice(this.vari.tableData.length,0,record);
    this.vari.tableData = this.vari.tableData.concat(record);
  }

  hasEditableRow():boolean{
    let data = this.cache.detail;
    if( data === undefined || data === null ) return false;

    for(let attr in this.cache.detail){
      if( this.cache.detail[attr].edit )
        return true;
    }

    return false;
  }

  startEdit(rowIndex:number,uuid:string,event){
    event.preventDefault();
    this.cache.detail[uuid].edit = true;
    // this.fillDefault(rowIndex,uuid);
    // this.msg('开启编辑状态的行是：' + rowIndex + ',' + uuid);
  }

  cancelEdit(rowIndex:number,uuid:string){
    this.cache.detail[uuid].edit = false;
  }

  saveEdit(rowIndex:number,uuid:string){
    let record:${modelName} = this.cache.detail[uuid].data;
    this.detailService.saveOne(record).subscribe(
      ( res:JsonResult ) => {
        if( res.code !=1 ){
          this.msg(res.msg);
          return
        }

        this.msg('保存完毕！');
        let newArray = this.vari.tableData.filter(
          item => item.uuid !== uuid
        )
        let newRecord:${modelName} = res.data;
        console.log('修改后返回的对象是：');
        console.log(newRecord);
        newRecord.uuid = UUID.UUID();
        newArray.splice(rowIndex,0,{...newRecord})
        this.vari.tableData = newArray;
        this.updateEditCache();// 更新编辑用缓存数据
        this.updateStatistics();// 更新统计数据
        this.cache.detail[newRecord.uuid].edit = false;
      },error => this.msg('保存时出现异常！')
    )

  }

  // 删除行 - 仅前端
  deleteRow_front(rowIndex:number,uuid:string){
    this.vari.tableData = this.vari.tableData.filter(
      item => item.uuid !== uuid
    )
  }
  
  /**
   * 删除一行数据：
   * 1. 如果没有iid ，那么只要删除前端 + 更新编辑用缓存数据
   * 2. 删除后端成功后，删除前端 + 更新编辑用缓存数据
   * @param rowIndex 行序号，从0开始
   * @param uuid 行属性uuid
   */
  deleteRow(rowIndex:number,uuid:string){
    let iid = this.vari.tableData[rowIndex].iid;
    if( iid === undefined || iid === null || iid<=0 ){// 仅仅删除前端
      this.deleteRow_front(rowIndex,uuid);
      this.updateEditCache();
      this.updateStatistics();
    }else{// 删除后端成功后：删除前端 + 更新编辑用缓存
      this.detailService.deleteByPrimaryKey(this.vari.tableData[rowIndex].iid).subscribe(
        ( res:JsonResult ) => {
          if( res.code != 1 ){
            this.msg(res.msg);
            return
          }
  
          this.deleteRow_front(rowIndex,uuid);
          this.updateEditCache();
          this.updateStatistics();
          this.msg('删除完毕！');
        },error => this.msg('删除时出现异常！')
      )
    }
  }

  cancelDeleteRow(){
    // console.log('取消了删除行');
  }

  copyRow(rowIndex:number,uuid:string){
    let iid = this.vari.tableData[rowIndex].iid;
    if( iid === undefined || iid === null || iid <=0 ){
      this.msg('该行还未保存，不可复制！');
      return
    }

    let newRecord:${modelName} = {...this.vari.tableData[rowIndex]};
    newRecord.iid = 0;
    this.detailService.insert(newRecord).subscribe(
      ( res:JsonResult ) =>{
        if( res.code !==1 ){
          this.msg(res.msg);
          return
        }

        newRecord.uuid = UUID.UUID();
        this.vari.tableData = this.vari.tableData.concat(newRecord);
        this.updateEditCache();
        this.msg('复制完毕！');
      },error => this.msg('复制时出现异常！')
    )
  }


/**
 * 将数据库 table_style 表中设置为枚举类型的下拉选项的字段
 * 显示为其值对应的文本
 * @param record 行数据，单个实体类对象
 * @param fieldName 字段名称
 */
  convertRaw2Show(record:${modelName},tsRow:TableStyle):string{
    return this.breadField.convertRaw2Show(record,tsRow);
  }


  // 从显示模式切换到编辑模式时为下拉框填充数据
  fillDefault(rowIndex:number,uuid:string){
    console.log('进入fillDefault');
    this.vari.tableStyle.forEach(ele => {
      switch(ele.ctl_type){
        case('optsEnum'):
          console.log('是 optsEnum 类型的控件，赋值前：' + this.cache.detail[uuid].data[ele.field_name]);
          this.cache.detail[uuid].data[ele.field_name] = this.vari.tableData[rowIndex][ele.field_name];
          console.log('赋值后：' + this.cache.detail[uuid].data[ele.field_name]);
          break;
        default:
          break;
      }
    });
    
  }

  setTest(){
    let aa:string = '1+2-3*4/2';
    const gg = ${regSplit};
    let bb = aa.split(gg);

    console.log(bb);
  }

  // 最初的设计是视图中存在数据表中没有的字段，完全从其他字段通过逻辑或者算数运算得来
  // 后来发现统计上会有问题，放弃该做法，采用较死板的方式：
  // 算数运算中在等于号左边的字段也要在数据表中有字段存储数据，在编辑模式中为不可编辑。
  generateCol(dataRow:${modelName},tsRow:TableStyle){
    return this.breadField.getGeneratedResult(dataRow,tsRow);
  }

  colHeaderAlign(fieldName:string):string{
    let tsRow:TableStyle = this.vari.tableStyle.filter( item => item.field_name === fieldName )[0];
    return tsRow.align_header;
  }

  colContentAlign(fieldName:string):string{
    let tsRow:TableStyle = this.vari.tableStyle.filter( item => item.field_name === fieldName )[0];
    return tsRow.align_cell;
  }

  // 更新统计数据
  updateStatistics(){
    if( this.vari.tableData.length<=0 ) return

    this.vari.statistics = null;
    this.vari.statistics = {};

    // 遍历字段构建汇总对象
    let arr = this.vari.tableStyle.filter(item=>item.statistics);
    if( arr.length<=0 ) return;
    arr.forEach(ele => {
      this.vari.statistics[ele.field_name] = { value:0,label:'' };
    });
    

    this.vari.tableData.forEach(ele => {
      this.vari.tableStyle.forEach(ele1 => {
        let field:string = ele1.field_name;
        if( ele1.statistics ){
          let curValue:number = ele[field] === undefined?0:ele[field];
          curValue = curValue === null?0:curValue;
          this.vari.statistics[field].value = this.vari.statistics[field].value + curValue;
          this.vari.statistics[field].label = Number(this.vari.statistics[field].value).toFixed(1);
        }
      });
    });
  }


  refreshTable(){
    this.pullTableData();
  }


  resolveFormula(dataRow:${modelName},tsRow:TableStyle):string{
    if( !tsRow.statistics ) return

    return this.breadField.useFormatter(dataRow,tsRow)
  }

  // 编辑状态下，编辑文本变动则对应修改其相关公式关联的字段的值
  inputChange(dataRow:${modelName},tsRow:TableStyle){
    let arr:{ fieldName:string,value:number }[] = this.breadField.triggerFormula(dataRow,tsRow,this.vari.tableStyle);
    if( arr === undefined || arr === null || arr.length<=0 ) return
    arr.forEach(ele => {
      this.cache.detail[dataRow.uuid].data[ele.fieldName] = ele.value;
    });
  }
  

}
` 
    // 在2020年1月18日22:57:39第二次制作，所以返回 ret1
    return ret1;
  }

// 主体方法结束  
}

export class DBConfig{
  server:string;
  port:number;
  dbName:string;
  driver:string;
  dbUserName:string;
  dbUserPwd:string;  
}

export class DynamicQueryParams{
  dbConfig:DBConfig;
  sql:string;

  constructor(){
    this.dbConfig = new DBConfig();
  }
}
